﻿namespace ReplayDataFile
{
    using System;
    using System.Collections.Generic;
    using System.Diagnostics;
    using System.IO;
    using System.Runtime.CompilerServices;
    using System.Threading;

    public class BlockFileWriter : BlockFile
    {
        private SortedDictionary<long, byte[]> dataBufDict;
        private SortedDictionary<long, byte[]> indexBufDict;
        private Dictionary<int, DataRecord> lastRecordDict;
        private List<DataRecord> recordList;
        private RecordZipper zipRecord;

        public event AddNewDataTypeEvnetHandler AddNewDataType;

        public event FileOverflowEventHandler Overflow;

        public BlockFileWriter(string fileName, uint fileId, int blockCount, int blockSize, int maxDataTypes, ICollection<DataTypeInfo> dataTypes, long firstRecordNo) : base(fileName)
        {
            this.dataBufDict = new SortedDictionary<long, byte[]>();
            this.indexBufDict = new SortedDictionary<long, byte[]>();
            this.recordList = new List<DataRecord>();
            this.zipRecord = new RecordZipper();
            this.lastRecordDict = new Dictionary<int, DataRecord>();
            if ((dataTypes != null) && (blockCount < (dataTypes.Count * 2)))
            {
                throw new Exception("数据块数目太少，至少为数据类型数目的2倍！");
            }
            base.Header = new BlockFileHeader();
            base.Header.BlockCount = blockCount;
            base.Header.BlockSize = blockSize;
            base.Header.FileId = fileId;
            base.Header.FirstRecordNo = firstRecordNo;
            base.Header.RecordNo = firstRecordNo;
            base.DataTypeDir = new DataTypeDirectory(maxDataTypes, dataTypes);
            base.BlockAllocTable = new BlockAllocateTable(blockCount);
            base.Header.BlockAllocTableSize = base.BlockAllocTable.Size;
            base.Header.DataTypeDirSize = base.DataTypeDir.Size;
            this.IsZipRecord = true;
            this.RecordIndexInterval = 100;
            this.IsAutoAppendBlock = false;
        }

        public bool AddDataType(int dataType, bool hasIndex)
        {
            if (base.BlockAllocTable.GetFreeBlockCount() < 2)
            {
                return false;
            }
            DataTypeInfo info = new DataTypeInfo {
                DataType = dataType,
                Name = "",
                HasIndex = hasIndex
            };
            if (!base.DataTypeDir.AddDataType(info))
            {
                return false;
            }
            DataTypeDirEntry dirEntry = base.DataTypeDir.GetDirEntry(dataType);
            dirEntry.IsWriteIndex = hasIndex;
            if (hasIndex && !this.AllocFirstBlock(dirEntry, false))
            {
                return false;
            }
            if (!this.AllocFirstBlock(dirEntry, true))
            {
                return false;
            }
            if (this.AddNewDataType != null)
            {
                this.AddNewDataType(dirEntry);
            }
            return true;
        }

        protected void AddNewTimeSegment(DateTime beginTime, long recordNo)
        {
            TimeSegment segment;
            segment = new TimeSegment {
                BeginTime = beginTime,
                BeginRecordNo = recordNo,
                EndTime = beginTime,
                EndRecordNo = recordNo
            };
            base.Header.TimeSegments.Add(segment);
        }

        private bool AllocFirstBlock(DataTypeDirEntry entry, bool isDataBlock)
        {
            int blockIndex = -1;
            if (this.IsAutoAppendBlock)
            {
                blockIndex = base.BlockAllocTable.GetFreeBlock();
            }
            else if (isDataBlock)
            {
                int beginBlockIndex = -1;
                int endBlockIndex = -1;
                if (base.BlockAllocTable.GetMaxFreeRegion(ref beginBlockIndex, ref endBlockIndex))
                {
                    blockIndex = ((beginBlockIndex + endBlockIndex) + 1) / 2;
                }
            }
            else
            {
                blockIndex = base.BlockAllocTable.GetFreeBlock();
            }
            if (blockIndex < 0)
            {
                return false;
            }
            base.BlockAllocTable.AllocBlock(blockIndex, -1, -1);
            if (isDataBlock)
            {
                entry.FirstDataBlock = blockIndex;
                entry.CurDataBlock = blockIndex;
                entry.DataBlockOffset = 0;
            }
            else
            {
                entry.FirstIndexBlock = blockIndex;
                entry.CurIndexBlock = blockIndex;
                entry.IndexBlockOffset = 0;
            }
            return true;
        }

        public override bool Close()
        {
            lock (this)
            {
                this.Flush();
                return base.Close();
            }
        }

        private bool CreateNewFile()
        {
            base.fStream = new FileStream(base.fileName, FileMode.OpenOrCreate, FileAccess.ReadWrite, FileShare.Read);
            long num = this.CreateNewFileSize();
            if (!this.IsAutoAppendBlock)
            {
                base.fStream.Seek(num - 1L, SeekOrigin.Begin);
                base.fStream.WriteByte(0);
            }
            foreach (DataTypeDirEntry entry in base.DataTypeDir.DirEntries)
            {
                if (entry.IsWriteIndex)
                {
                    this.AllocFirstBlock(entry, false);
                }
            }
            foreach (DataTypeDirEntry entry in base.DataTypeDir.DirEntries)
            {
                this.AllocFirstBlock(entry, true);
            }
            this.Flush();
            return true;
        }

        private long CreateNewFileSize()
        {
            return (((BlockFileHeader.Size + base.DataTypeDir.Size) + base.BlockAllocTable.Size) + (base.Header.BlockCount * base.Header.BlockSize));
        }

        public bool DeleteDataType(int dataType, bool isFlush)
        {
            DataTypeDirEntry dirEntry = base.DataTypeDir.GetDirEntry(dataType);
            if (dirEntry != null)
            {
                List<int> allocBlockList = base.BlockAllocTable.GetAllocBlockList(dirEntry.FirstDataBlock);
                allocBlockList.AddRange(base.BlockAllocTable.GetAllocBlockList(dirEntry.FirstIndexBlock));
                foreach (int num in allocBlockList)
                {
                    base.BlockAllocTable.InitDataBlock(num);
                }
                if (isFlush)
                {
                    this.Flush();
                }
            }
            return false;
        }

        public void Flush()
        {
            if (base.fStream != null)
            {
                List<DataRecord> recList = new List<DataRecord>();
                lock (this.recordList)
                {
                    recList.AddRange(this.recordList);
                    this.recordList.Clear();
                }
                List<DataRecord> remainRecordList = null;
                lock (this)
                {
                    if (recList.Count > 0)
                    {
                        remainRecordList = this.WriteRecordAct(recList);
                        if (remainRecordList != null)
                        {
                            BlockFileHeader header = base.Header;
                            header.RecordNo -= remainRecordList.Count;
                        }
                    }
                    base.fStream.Seek(0L, SeekOrigin.Begin);
                    byte[] buffer = base.Header.ToByteBuffer();
                    base.fStream.Write(buffer, 0, buffer.Length);
                    buffer = base.DataTypeDir.ToByteBuffer();
                    base.fStream.Write(buffer, 0, buffer.Length);
                    buffer = base.BlockAllocTable.ToByteBuffer();
                    base.fStream.Write(buffer, 0, buffer.Length);
                    base.fStream.Flush();
                }
                if (remainRecordList != null)
                {
                    base.Close();
                    if (this.Overflow != null)
                    {
                        this.Overflow(base.Header.RecordNo, remainRecordList);
                    }
                }
            }
        }

        private bool HasEnoughDataSpace(DataTypeDirEntry entry, DataRecord rec)
        {
            int num = base.Header.BlockSize - entry.DataBlockOffset;
            return ((num >= rec.Size) || (base.BlockAllocTable.GetFreeBlock() >= 0));
        }

        private bool HasEnoughIndexSpace(DataTypeDirEntry entry, DataRecord rec)
        {
            int num = base.Header.BlockSize - entry.IndexBlockOffset;
            return ((num >= rec.Size) || (base.BlockAllocTable.GetFreeBlock() >= 0));
        }

        private bool HasEnoughSpaceWriteRecord(DataRecord rec)
        {
            if (this.IsAddNewTimeSegment(rec) && (base.Header.TimeSegments.Count >= base.Header.MaxTimeSegmentCount))
            {
                return false;
            }
            DataTypeDirEntry dirEntry = base.DataTypeDir.GetDirEntry(rec.DataType);
            return (this.HasEnoughDataSpace(dirEntry, rec) && this.HasEnoughIndexSpace(dirEntry, rec));
        }

        private bool IsAddNewTimeSegment(DataRecord rec)
        {
            if ((rec.RecType == DataRecordType.ClockHopRecord) && (rec.DataType == -1))
            {
                BinaryReader reader = new BinaryReader(new MemoryStream(rec.DataBuffer));
                if (Math.Abs(reader.ReadInt64()) >= 0xea60L)
                {
                    return true;
                }
            }
            return false;
        }

        private bool IsWriteIndexRecord(DataRecord rec)
        {
            DataTypeDirEntry dirEntry = base.DataTypeDir.GetDirEntry(rec.DataType);
            if (!dirEntry.IsWriteIndex)
            {
                return false;
            }
            if (dirEntry.TotalRecordFromStartup == 0)
            {
                return true;
            }
            switch (rec.RecType)
            {
                case DataRecordType.FullDataRecord:
                {
                    if (!this.lastRecordDict.ContainsKey(rec.DataType))
                    {
                        break;
                    }
                    DataRecord record = this.lastRecordDict[rec.DataType];
                    if (DataRecord.IsDataRecordType(record.RecType))
                    {
                        break;
                    }
                    return true;
                }
                case DataRecordType.ChangeDataRecord:
                    return false;

                default:
                    return true;
            }
            return (dirEntry.ToWriteCount >= this.RecordIndexInterval);
        }

        public override bool Open()
        {
            return this.Open(File.Exists(base.fileName));
        }

        internal bool Open(bool isOpenExist)
        {
            BlockFileWriter writer;
            if (base.isOpened)
            {
                return false;
            }
            Monitor.Enter(writer = this);
            try
            {
                base.isOpened = false;
                if (isOpenExist)
                {
                    base.isOpened = base.OpenExist(FileAccess.ReadWrite, FileShare.Read);
                }
                else
                {
                    base.isOpened = this.CreateNewFile();
                }
            }
            catch (Exception exception)
            {
                Trace.WriteLine(exception.Message);
                base.isOpened = false;
            }
            finally
            {
                Monitor.Exit(writer);
            }
            return base.isOpened;
        }

        private byte[] PackRecord(DataRecord rec)
        {
            bool isCompactHead = false;
            DataRecord preRecord = null;
            if ((rec.ZipType != 0) && this.lastRecordDict.ContainsKey(rec.DataType))
            {
                preRecord = this.lastRecordDict[rec.DataType];
                isCompactHead = rec.CanCompactHeader(preRecord);
            }
            return rec.ToByteBuffer(isCompactHead, preRecord);
        }

        private void ProcessTimeback(DataRecord rec)
        {
            if (this.IsAddNewTimeSegment(rec) || (base.Header.TimeSegments.Count == 0))
            {
                this.AddNewTimeSegment(rec.Time, rec.RecordNo);
            }
            else
            {
                DateTime endTime = base.LastTimeSegment.EndTime;
                base.LastTimeSegment.EndRecordNo = rec.RecordNo;
                if (rec.Time > endTime)
                {
                    base.LastTimeSegment.EndTime = rec.Time;
                }
            }
        }

        public bool WriteRecord(DataRecord rec)
        {
            if (!base.isOpened)
            {
                return false;
            }
            if (rec == null)
            {
                return false;
            }
            if (base.DataTypeDir.GetDirEntry(rec.DataType) == null)
            {
                return false;
            }
            rec.Time = new DateTime((rec.Time.Ticks / 0x2710L) * 0x2710L);
            lock (this.recordList)
            {
                long num;
                BlockFileHeader header = base.Header;
                header.RecordNo = (num = header.RecordNo) + 1L;
                rec.RecordNo = num;
                this.recordList.Add(rec);
            }
            return true;
        }

        private List<DataRecord> WriteRecordAct(List<DataRecord> recList)
        {
            int count = recList.Count;
            int index = 0;
            for (int i = 0; i < count; i++)
            {
                DataRecord rec = recList[i];
                if (!this.WriteSingleRecordAct(rec))
                {
                    break;
                }
                index++;
            }
            foreach (KeyValuePair<long, byte[]> pair in this.dataBufDict)
            {
                base.fStream.Seek(pair.Key, SeekOrigin.Begin);
                base.fStream.Write(pair.Value, 0, pair.Value.Length);
            }
            this.dataBufDict.Clear();
            base.fStream.Flush();
            foreach (KeyValuePair<long, byte[]> pair in this.indexBufDict)
            {
                base.fStream.Seek(pair.Key, SeekOrigin.Begin);
                base.fStream.Write(pair.Value, 0, pair.Value.Length);
            }
            this.indexBufDict.Clear();
            base.fStream.Flush();
            if (index < count)
            {
                return recList.GetRange(index, count - index);
            }
            return null;
        }

        private bool WriteRecordData(DataRecord rec)
        {
            DataTypeDirEntry dirEntry = base.DataTypeDir.GetDirEntry(rec.DataType);
            if ((dirEntry.CurDataBlock == -1) && !this.AllocFirstBlock(dirEntry, true))
            {
                return false;
            }
            bool flag = (rec.RecType == DataRecordType.FullDataRecord) || (rec.RecType == DataRecordType.ChangeDataRecord);
            BlockInfo blockInfo = base.BlockAllocTable.GetBlockInfo(dirEntry.CurDataBlock);
            if ((blockInfo.DataVersion == -1) && flag)
            {
                blockInfo.DataVersion = rec.DataVersion;
            }
            int num = base.Header.BlockSize - dirEntry.DataBlockOffset;
            if ((num < rec.Size) || ((blockInfo.DataVersion != rec.DataVersion) && flag))
            {
                int nextFreeBlock = base.BlockAllocTable.GetNextFreeBlock(dirEntry.CurDataBlock);
                Debug.Assert(nextFreeBlock >= 0);
                if (nextFreeBlock < 0)
                {
                    return false;
                }
                base.BlockAllocTable.AllocBlock(nextFreeBlock, dirEntry.CurDataBlock, rec.DataVersion);
                dirEntry.CurDataBlock = nextFreeBlock;
                dirEntry.DataBlockOffset = 0;
                base.BlockAllocTable.GetBlockInfo(nextFreeBlock).DataVersion = rec.DataVersion;
            }
            long key = base.GetDataBlockInFileOffset(dirEntry.CurDataBlock) + dirEntry.DataBlockOffset;
            byte[] buffer = this.PackRecord(rec);
            this.dataBufDict.Add(key, buffer);
            dirEntry.DataBlockOffset += buffer.Length;
            dirEntry.DataRecordCount++;
            dirEntry.DataRecordTotalBytes += buffer.Length;
            base.BlockAllocTable.WriteBlockUsedSpace(dirEntry.CurDataBlock, dirEntry.DataBlockOffset);
            dirEntry.ToWriteCount++;
            return true;
        }

        private bool WriteRecordIndex(DataRecord rec)
        {
            DataTypeDirEntry dirEntry = base.DataTypeDir.GetDirEntry(rec.DataType);
            dirEntry.ToWriteCount = 0;
            if ((dirEntry.CurIndexBlock == -1) && !this.AllocFirstBlock(dirEntry, false))
            {
                return false;
            }
            int num = base.Header.BlockSize - dirEntry.IndexBlockOffset;
            if (num < RecordIndex.Size)
            {
                int nextFreeBlock = base.BlockAllocTable.GetNextFreeBlock(dirEntry.CurIndexBlock);
                Debug.Assert(nextFreeBlock >= 0);
                if (nextFreeBlock < 0)
                {
                    return false;
                }
                base.BlockAllocTable.AllocBlock(nextFreeBlock, dirEntry.CurIndexBlock, rec.DataVersion);
                dirEntry.CurIndexBlock = nextFreeBlock;
                dirEntry.IndexBlockOffset = 0;
            }
            long key = base.GetDataBlockInFileOffset(dirEntry.CurIndexBlock) + dirEntry.IndexBlockOffset;
            byte[] buffer = new RecordIndex { RecordNo = rec.RecordNo, RecordNoInKind = dirEntry.DataRecordCount - 1, Block = dirEntry.CurDataBlock, OffsetInBlock = dirEntry.DataBlockOffset - rec.Size, RecordSize = rec.Size, Time = rec.Time }.ToByteBuffer();
            this.indexBufDict.Add(key, buffer);
            dirEntry.IndexBlockOffset += buffer.Length;
            dirEntry.IndexRecordCount++;
            dirEntry.TotalRecordFromStartup++;
            base.BlockAllocTable.WriteBlockUsedSpace(dirEntry.CurIndexBlock, dirEntry.IndexBlockOffset);
            return true;
        }

        private bool WriteSingleRecordAct(DataRecord rec)
        {
            if (!this.HasEnoughSpaceWriteRecord(rec))
            {
                return false;
            }
            bool flag = this.IsWriteIndexRecord(rec);
            if (this.IsZipRecord)
            {
                rec = this.zipRecord.Zip(rec, !flag);
            }
            if (!this.WriteRecordData(rec))
            {
                return false;
            }
            bool flag2 = true;
            if (flag)
            {
                flag2 = this.WriteRecordIndex(rec);
            }
            this.ProcessTimeback(rec);
            this.lastRecordDict[rec.DataType] = rec;
            return flag2;
        }

        public bool IsAutoAppendBlock { get; set; }

        public bool IsZipRecord { get; set; }

        public int RecordIndexInterval { get; set; }
    }
}

